#include <stdio.h>
#include <signal.h>
#include <malloc.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#include "cfd-proc.h"

void * __free_map(struct map * map
                  , void * args      __attribute__((unused))
                  , void * last_rval __attribute__((unused))
                  )
{
    free(map->name);
    free(map);
    return NULL;
}

void * __print_map(struct map * map
                  , void * args      __attribute__((unused))
                  , void * last_rval __attribute__((unused))
                  )
{
    printf("%s at %p [size: %lu]\n"
           , map->name
           , map->address
           , map->size
           );

    return NULL;
}

void * __search_last_map(struct map * map
                         , void * args
                         , void * last_rval
                         )
{
    if (! map->name)
        return last_rval;

    const char * d    = strrchr(map->name, '/');
    const char * name = d == NULL
        ? map->name
        : d
        ;

    if (strstr(name, args))
        return map;
    else
        return last_rval;
}


void * __search_first_map(struct map * map
                          , void * args
                          , void * last_rval
                          )
{
    if (last_rval)
        return last_rval;

    return __search_last_map(map, args, NULL);
}

/* ============================================ */

struct map * create_map(   const char * start
                         , const char * end
                         , const char * object
                         , struct map * last
                        )
{
    struct map * map =
        (struct map *) malloc (sizeof(*map));

    if (map == NULL)
        return NULL;

    map->address   = (void *) strtoul(start, NULL, 16);
    map->size      = strtoul(end, NULL, 16)
                     - (long) map->address;
    map->name      = strdup(object);
    map->next      = NULL;

    if (last)
        last->next = map;

    return map;
}

void * do_with_maps(struct map * map
                    , map_cmd    cmd
                    , void     * args
                    )
{
    void * last_rval = NULL;

    while (map != NULL)
    {
        struct map * this = map;
        map = this->next;

        last_rval = cmd(this
                        , args
                        , last_rval
                        );
    }

    return last_rval;
}

/* ============================================ */

void free_maps(struct map * map)
{
    do_with_maps(map
                 , __free_map
                 , NULL
                 );
}

void print_map(struct map * map)
{
    __print_map(map, NULL, NULL);
}


void print_maps(struct map * map)
{
    do_with_maps(map, __print_map, NULL);
}

struct map * find_first_map(struct map   * map
                            ,const char * name)
{
    return do_with_maps(map
                        , __search_first_map
                        , (void *) name);
}

struct map * find_last_map(struct map   * map
                           ,const char * name)
{
    return do_with_maps(map
                        , __search_last_map
                        , (void *) name);
}

/*============================================ */

struct map * parse_proc_maps(pid_t pid
                             , const char * filter_mode
                             )
{
    struct map * map_list = NULL;
    struct map * last     = NULL;

    char maps_path[MAX_PATH];

    if (NULL == filter_mode)
        return NULL;

    bzero(maps_path, sizeof(maps_path));
    snprintf(maps_path
             , sizeof(maps_path)
             , "/proc/%d/maps"
             , pid
             );

    FILE * maps = fopen(maps_path, "r");

    if (NULL == maps)
    {
        fprintf(stderr
                , "Couldn't open maps: %s : %s\n"
                , maps_path
                , strerror(errno)
                );

        return NULL;
    }

    char buffer [MAX_PATH + 32*6];

    while(fgets(buffer, sizeof(buffer), maps) != NULL)
    {
        char  * tend   = NULL;

        char  * start  = NULL
            , * end    = NULL
            , * mode   = NULL
            , * offset = NULL
            , * device = NULL
            , * size   = NULL
            , * object = NULL
            ;

        start  = strtok_r(buffer,"-", &tend);
        end    = strtok_r(NULL,  " ", &tend);
        mode   = strtok_r(NULL,  " ", &tend);
        offset = strtok_r(NULL,  " ", &tend);
        device = strtok_r(NULL,  " ", &tend);
        size   = strtok_r(NULL,  " ", &tend);
        object = strtok_r(NULL,  " ", &tend);

        if (object)
        {
            char * n = strchr(object, '\n');
            if (n)
                *n = 0;
        }

        if (strstr(mode, filter_mode))
        {
            last
                = create_map(start
                             , end
                             , object
                             , last
                             );
        }

        if (NULL == map_list)
            map_list = last;
    }

    fclose(maps);

    return map_list;
}

